{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "9c794bc7",
   "metadata": {},
   "source": [
    "# 构建检索问答链"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3d0f2c3-3bd9-4de1-bbd2-e0e2b09161c8",
   "metadata": {},
   "source": [
    "在 `C5 搭建数据库` 章节，我们已经介绍了如何根据自己的本地知识文档，搭建一个向量知识库。 在接下来的内容里，我们将使用搭建好的向量数据库，对 query 查询问题进行召回，并将召回结果和 query 结合起来构建 prompt，输入到大模型中进行问答。   "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95d8d968-8d98-47b9-8885-dc17d24dce76",
   "metadata": {},
   "source": [
    "## 1. 加载向量数据库\n",
    "\n",
    "首先，我们加载在前一章已经构建的向量数据库。注意，此处你需要使用和构建时相同的 Emedding。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9e58c2a-8690-4aa9-aadd-2129e79a3e9d",
   "metadata": {},
   "source": [
    "### 设置embedding - HuggingFaceEmbeddings"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "2cb1297f-1720-40ef-8ea8-bb58bc7e39a5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用 HuggingFaceEmbeddings API， 免费\n",
    "from langchain_community.embeddings import HuggingFaceEmbeddings\n",
    "\n",
    "from langchain.vectorstores.chroma import Chroma\n",
    "# from langchain_community.vectorstores import Chroma"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f5ea91da-0c42-4edd-9749-10536ea89f76",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义 Embeddings\n",
    "embeddings = HuggingFaceEmbeddings(model_name = \"sentence-transformers/all-mpnet-base-v2\")\n",
    "\n",
    "# 向量数据库持久化路径\n",
    "persist_directory = '../../data_base/vector_db/chroma'\n",
    "# 加载数据库\n",
    "vectordb = Chroma(\n",
    "    persist_directory=persist_directory,  # 允许我们将persist_directory目录保存到磁盘上\n",
    "    embedding_function=embeddings\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7affbed-f36e-4700-a1d9-c5d88917fff5",
   "metadata": {},
   "source": [
    "### 设置embedding - zhipu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "8f66d376-8140-4224-bdfb-360b60aef43f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import sys\n",
    "# sys.path.append(\"../C3 搭建知识库\") # 将父目录放入系统路径中\n",
    "\n",
    "# 使用智谱 Embedding API，注意，需要将上一章实现的封装代码下载到本地\n",
    "from zhipuai_embedding import ZhipuAIEmbeddings\n",
    "\n",
    "from langchain.vectorstores.chroma import Chroma\n",
    "\n",
    "# from langchain_community.embeddings.openai import OpenAIEmbeddings\n",
    "# from langchain_community.embeddings import HuggingFaceEmbeddings"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "af4a941c-e58d-4515-bdec-55799a4f366a",
   "metadata": {},
   "source": [
    "从环境变量中加载你的 API_KEY"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "59ed5506-9aa2-4777-b594-e97a9148f171",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'05dc01cafdae67456b454b09a7548559.GWN9tBQtENjWuv1N'"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from dotenv import load_dotenv, find_dotenv\n",
    "import os\n",
    "\n",
    "_ = load_dotenv(find_dotenv())    # read local .env file\n",
    "zhipuai_api_key = os.environ['ZHIPUAI_API_KEY']\n",
    "\n",
    "zhipuai_api_key"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c696e67-1369-4687-86cb-a2e8101a2f38",
   "metadata": {},
   "source": [
    "加载向量数据库，其中包含了 ../../data_base/knowledge_db 下多个文档的 Embedding"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "1e277ec2-28e4-448d-ae39-e0f703017811",
   "metadata": {},
   "outputs": [],
   "source": [
    "# # 定义 Embeddings\n",
    "# # embeddings = ZhipuAIEmbeddings()\n",
    "# # embeddings = OpenAIEmbeddings()\n",
    "# embedding = ZhipuAIEmbeddings()\n",
    "\n",
    "# # 向量数据库持久化路径\n",
    "# # persist_directory = '../C3 搭建知识库/data_base/vector_db/chroma'\n",
    "# persist_directory = '../../data_base/vector_db/chroma'\n",
    "# # 加载数据库\n",
    "# vectordb = Chroma(\n",
    "#     persist_directory=persist_directory,  # 允许我们将persist_directory目录保存到磁盘上\n",
    "#     embedding_function=embedding\n",
    "# )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "9a606a7a-ff7b-487c-9e30-95f4b867d63f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "向量数据库已成功加载。\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from dotenv import load_dotenv, find_dotenv\n",
    "from langchain.vectorstores.chroma import Chroma\n",
    "from zhipuai_embedding import ZhipuAIEmbeddings  # 假设这是正确的导入路径\n",
    "\n",
    "\n",
    "_ = load_dotenv(find_dotenv())    # read local .env file\n",
    "zhipuai_api_key = os.environ['ZHIPUAI_API_KEY']\n",
    "\n",
    "# 定义持久化目录\n",
    "persist_directory = '../../data_base/vector_db/chroma'\n",
    "\n",
    "# 创建嵌入模型\n",
    "embedding = ZhipuAIEmbeddings()\n",
    "\n",
    "try:\n",
    "    # 加载持久化的 Chroma 向量数据库\n",
    "    vectordb = Chroma(\n",
    "        persist_directory=persist_directory,  # 允许我们将persist_directory目录保存到磁盘上\n",
    "        embedding_function=embedding\n",
    "    )\n",
    "    print(\"向量数据库已成功加载。\")\n",
    "except Exception as e:\n",
    "    print(f\"加载向量数据库时发生错误: {e}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "7b0b1838-38a3-4666-8bc8-c4592a5a39d8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "向量库中存储的数量：60\n"
     ]
    }
   ],
   "source": [
    "print(f\"向量库中存储的数量：{vectordb._collection.count()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7f999e13-3499-40b0-a6f3-720e48c5b18e",
   "metadata": {},
   "source": [
    "### 设置embedding - ollama"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d443ec32-790e-44d0-8377-aff6dc933347",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/4f/lx3tlt3d6fz2bys8ct46fzxh0000gn/T/ipykernel_90332/2072186869.py:14: LangChainDeprecationWarning: The class `OllamaEmbeddings` was deprecated in LangChain 0.3.1 and will be removed in 1.0.0. An updated version of the class exists in the :class:`~langchain-ollama package and should be used instead. To use it run `pip install -U :class:`~langchain-ollama` and import as `from :class:`~langchain_ollama import OllamaEmbeddings``.\n",
      "  oembed = OllamaEmbeddings(base_url=\"http://localhost:11434\", model=\"nomic-embed-text\")\n",
      "/var/folders/4f/lx3tlt3d6fz2bys8ct46fzxh0000gn/T/ipykernel_90332/2072186869.py:17: LangChainDeprecationWarning: The class `Chroma` was deprecated in LangChain 0.2.9 and will be removed in 1.0. An updated version of the class exists in the :class:`~langchain-chroma package and should be used instead. To use it run `pip install -U :class:`~langchain-chroma` and import as `from :class:`~langchain_chroma import Chroma``.\n",
      "  vectordb = Chroma(\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "向量数据库已成功加载。\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from langchain.vectorstores.chroma import Chroma\n",
    "from langchain_community.embeddings import OllamaEmbeddings\n",
    "from langchain.embeddings import OllamaEmbeddings\n",
    "\n",
    "\n",
    "# 定义持久化目录\n",
    "persist_directory = '../../data_base/vector_db/chroma'\n",
    "\n",
    "\n",
    "# 创建嵌入模型\n",
    "# 初始化Ollama嵌入模型\n",
    "# 假定Ollama服务已经在本地运行\n",
    "oembed = OllamaEmbeddings(base_url=\"http://localhost:11434\", model=\"nomic-embed-text\")\n",
    "try:\n",
    "    # 加载持久化的 Chroma 向量数据库\n",
    "    vectordb = Chroma(\n",
    "        persist_directory=persist_directory,  # 允许我们将persist_directory目录保存到磁盘上\n",
    "        embedding_function=oembed\n",
    "    )\n",
    "    print(\"向量数据库已成功加载。\")\n",
    "except Exception as e:\n",
    "    print(f\"加载向量数据库时发生错误: {e}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d64f262-0638-4aa3-bd89-ef74e33f5238",
   "metadata": {},
   "source": [
    "我们可以测试一下加载的向量数据库，使用一个问题 query 进行向量检索。如下代码会在向量数据库中根据相似性进行检索，返回前 k 个最相似的文档。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f5912d3d-9517-465c-8b82-cdd1eab27a30",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "向量库中存储的数量：20\n"
     ]
    }
   ],
   "source": [
    "print(f\"向量库中存储的数量：{vectordb._collection.count()}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c8a68dc0-4f4c-433b-a367-44b5cffe8516",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "检索到的内容数：3\n"
     ]
    }
   ],
   "source": [
    "question = \"什么是机器学习?\"\n",
    "docs = vectordb.similarity_search(question,k=3)\n",
    "print(f\"检索到的内容数：{len(docs)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09852d2a-aeda-4822-bf56-782a0397df3c",
   "metadata": {},
   "source": [
    "打印一下检索到的内容"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "d2f492bf-197b-4f94-820c-2d88c17754d6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "检索到的第0个内容: \n",
      " 有点飘的时候再回来啃都来得及；每个公式的解析和推导我们都力(zhi)争(neng)以本科数学基础的视角进行讲解，所以超纲的数学知识\n",
      "我们通常都会以附录和参考文献的形式给出，感兴趣的同学可以继续沿着我们给的资料进行深入学习；若南瓜书里没有你想要查阅的公式，或者你发现南瓜书哪个地方有错误，请毫不犹豫地去我们GitHub的\n",
      "Issues（地址：https://github.com/datawhalechina/pumpkin-book/issues）进行反馈，在对应版块\n",
      "提交你希望补充的公式编号或者勘误信息，我们通常会在24小时以内给您回复，超过24小时未回复的\n",
      "话可以微信联系我们（微信号：at-Sm1les）；\n",
      "配套视频教程：https://www.bilibili.com/video/BV1Mh411e7VU\n",
      "在线阅读地址：https://datawhalechina.github.io/pumpkin-book（仅供第1版）\n",
      "最新版PDF获取地址：https://github.com/datawhalechina/pumpkin-book/releases\n",
      "编委会\n",
      "-----------------------------------------------------\n",
      "检索到的第1个内容: \n",
      " 式(6.70)的推导\n",
      ".....................................586.6.5\n",
      "核对数几率回归......................................60→_→\n",
      "配套视频教程：https://www.bilibili.com/video/BV1Mh411e7VU←_←\n",
      "-----------------------------------------------------\n",
      "检索到的第2个内容: \n",
      " 怎么理解特征学习\n",
      "....................................46\n",
      "第6章支持向量机\n",
      "476.1\n",
      "间隔与支持向量..........................................476.1.1\n",
      "图6.1的解释.......................................476.1.2\n",
      "式(6.1)的解释......................................476.1.3\n",
      "式(6.2)的推导......................................476.1.4\n",
      "式(6.3)的推导......................................476.1.5\n",
      "式(6.4)的推导......................................486.1.6\n",
      "式(6.5)的解释......................................486.2\n",
      "对偶问题\n",
      "-----------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "for i, doc in enumerate(docs):\n",
    "    print(f\"检索到的第{i}个内容: \\n {doc.page_content}\", end=\"\\n-----------------------------------------------------\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f7f8dbd-ecd5-449d-9753-aedc2b74289c",
   "metadata": {},
   "source": [
    "## 2. 创建一个 LLM"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "026bd74f-3dd0-496e-905b-950a444bb7a7",
   "metadata": {},
   "source": [
    "在这里，我们调用 OpenAI 的 API 创建一个 LLM，当然你也可以使用其他 LLM 的 API 进行创建"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "745d50cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import os \n",
    "# OPENAI_API_KEY = os.environ[\"OPENAI_API_KEY\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "fca9fa08-ce50-478f-b0a4-c24166262dc5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# from langchain_openai import ChatOpenAI\n",
    "# llm = ChatOpenAI(model_name = \"gpt-3.5-turbo\", temperature = 0)\n",
    "\n",
    "# llm.invoke(\"请你自我介绍一下自己！\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "d7fe25e4-e98e-4cb7-ac95-40e0e7b14ce8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 需要下载源码\n",
    "from zhipuai_llm import ZhipuAILLM\n",
    "\n",
    "from dotenv import find_dotenv, load_dotenv\n",
    "import os\n",
    "\n",
    "# 读取本地/项目的环境变量。\n",
    "\n",
    "# find_dotenv()寻找并定位.env文件的路径\n",
    "# load_dotenv()读取该.env文件，并将其中的环境变量加载到当前的运行环境中\n",
    "# 如果你设置的是全局的环境变量，这行代码则没有任何作用。\n",
    "_ = load_dotenv(find_dotenv())\n",
    "\n",
    "# 获取环境变量 API_KEY\n",
    "api_key = os.environ[\"ZHIPUAI_API_KEY\"] #填写控制台中获取的 APIKey 信息\n",
    "llm = ZhipuAILLM(model=\"chatglm_std\", temperature=0, api_key=api_key)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f361e27-cafb-48bf-bb41-50c9cb3a4f7e",
   "metadata": {},
   "source": [
    "## 3. 构建检索问答链"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "91be03f4-264d-45cb-bebd-223c1c5747fd",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.prompts import PromptTemplate\n",
    "\n",
    "template = \"\"\"使用以下上下文来回答最后的问题。如果你不知道答案，就说你不知道，不要试图编造答\n",
    "案。最多使用三句话。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问！”。\n",
    "{context}\n",
    "问题: {question}\n",
    "\"\"\"\n",
    "\n",
    "QA_CHAIN_PROMPT = PromptTemplate(input_variables=[\"context\",\"question\"],\n",
    "                                 template=template)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2d06d7f-1dca-4d10-b5cd-3a23e9d91200",
   "metadata": {},
   "source": [
    "再创建一个基于模板的检索链："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "8b05eb57-edf5-4b35-9538-42c2b8f5cc16",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.chains import RetrievalQA\n",
    "\n",
    "qa_chain = RetrievalQA.from_chain_type(llm,\n",
    "                                       retriever=vectordb.as_retriever(),\n",
    "                                       return_source_documents=True,\n",
    "                                       chain_type_kwargs={\"prompt\":QA_CHAIN_PROMPT})\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c08ac734-6693-422d-97e1-50f140e2d358",
   "metadata": {},
   "source": [
    "创建检索 QA 链的方法 RetrievalQA.from_chain_type() 有如下参数：\n",
    "- llm：指定使用的 LLM\n",
    "- 指定 chain type : RetrievalQA.from_chain_type(chain_type=\"map_reduce\")，也可以利用load_qa_chain()方法指定chain type。\n",
    "- 自定义 prompt ：通过在RetrievalQA.from_chain_type()方法中，指定chain_type_kwargs参数，而该参数：chain_type_kwargs = {\"prompt\": PROMPT}\n",
    "- 返回源文档：通过RetrievalQA.from_chain_type()方法中指定：return_source_documents=True参数；也可以使用RetrievalQAWithSourceChain()方法，返回源文档的引用（坐标或者叫主键、索引）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "503a7972-a673-41ca-a028-647169d19fcb",
   "metadata": {},
   "source": [
    "## 4.检索问答链效果测试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "a610e223-64c2-4865-b049-b47a36262a50",
   "metadata": {},
   "outputs": [],
   "source": [
    "question_1 = \"什么是南瓜书？\"\n",
    "question_2 = \"王阳明是谁？\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "acc2223f-6fb5-4504-bfcd-ac74ca9ff2fa",
   "metadata": {},
   "source": [
    "### 4.1 基于召回结果和 query 结合起来构建的 prompt 效果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "1eb5c5c3-9958-44f5-9fbc-f867de6c5042",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/4f/lx3tlt3d6fz2bys8ct46fzxh0000gn/T/ipykernel_90332/1194333697.py:1: LangChainDeprecationWarning: The method `Chain.__call__` was deprecated in langchain 0.1.0 and will be removed in 1.0. Use :meth:`~invoke` instead.\n",
      "  result = qa_chain({\"query\": question_1})\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "大模型+知识库后回答 question_1 的结果：\n",
      "南瓜书是《机器学习公式详解》的昵称，是一本以本科数学基础为视角，详细讲解机器学习中的公式推导和解析的书籍。谢谢你的提问！\n"
     ]
    }
   ],
   "source": [
    "result = qa_chain({\"query\": question_1})\n",
    "print(\"大模型+知识库后回答 question_1 的结果：\")\n",
    "print(result[\"result\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "37a0f3c4-4c50-4b73-a4b3-674825a366f1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "大模型+知识库后回答 question_2 的结果：\n",
      "我不知道王阳明是谁。谢谢你的提问！\n"
     ]
    }
   ],
   "source": [
    "result = qa_chain({\"query\": question_2})\n",
    "print(\"大模型+知识库后回答 question_2 的结果：\")\n",
    "print(result[\"result\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4195cfa-1fc8-41a9-8984-91f2e5fbe013",
   "metadata": {},
   "source": [
    "### 4.2 大模型自己回答的效果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "569fbe28-2e2d-4042-b3a1-65326842bdc9",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/4f/lx3tlt3d6fz2bys8ct46fzxh0000gn/T/ipykernel_90332/2093528877.py:5: LangChainDeprecationWarning: The method `BaseLLM.predict` was deprecated in langchain-core 0.1.7 and will be removed in 1.0. Use :meth:`~invoke` instead.\n",
      "  llm.predict(prompt_template)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'南瓜书是一种以南瓜为主题的童话故事书。这类书籍通常以南瓜作为故事的主要元素，通过富有想象力的故事向孩子们传达一些积极的价值观和道德观念。南瓜书的内容往往丰富多彩，既有教育意义，又富有趣味性，能够吸引孩子们的注意力，培养他们的阅读兴趣。\\n\\n南瓜书的故事情节多种多样，例如南瓜可以帮助主人公解决问题，或者南瓜本身就是一个有生命的角色，与其他角色一起经历冒险。这些故事通常具有温馨、奇幻的色彩，能够激发孩子们的想象力和创造力。\\n\\n在中国，南瓜书也可能与传统的节日和习俗有关，如中秋节、万圣节等，南瓜作为这些节日的象征之一，经常出现在相关的儿童读物中。通过南瓜书，孩子们不仅能享受到阅读的乐趣，还能更好地了解和传承中国的传统文化。'"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt_template = \"\"\"请回答下列问题:\n",
    "                            {}\"\"\".format(question_1)\n",
    "\n",
    "### 基于大模型的问答\n",
    "llm.predict(prompt_template)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "d0d3a813-db19-4be5-8926-ad8298e3e2b1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'王阳明，原名王守仁，字伯安，是明代著名的哲学家、军事家、教育家。他生活在15世纪末至16世纪初，是中国历史上一位极具影响力的思想家。王阳明提出了“知行合一”的哲学思想，主张知识和行动不可分割，人的道德和智慧应该通过实际行动来体现。\\n\\n王阳明还是心学的代表人物之一，他的心学思想强调“良知”即人的内在道德意识，认为道德和智慧存在于每个人的心中，主张通过内省和自我修养来实现个人的道德提升。他的学说对后世有着深远的影响，被后人尊称为“阳明先生”。\\n\\n在政治和军事上，王阳明也有卓越的表现，他曾任多地官员，平定了多次叛乱，并提出了许多有益于民生的政策。在教育上，他提倡教育平等，主张让更多的人接受教育，以提升整个社会的道德水平和文化素养。王阳明的思想和实践，至今仍在中国乃至世界范围内产生着广泛的影响。'"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt_template = \"\"\"请回答下列问题:\n",
    "                            {}\"\"\".format(question_2)\n",
    "\n",
    "### 基于大模型的问答\n",
    "llm.predict(prompt_template)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51b9ba4a-053d-409a-a632-63336c2bdf84",
   "metadata": {},
   "source": [
    "> ⭐ 通过以上两个问题，我们发现 LLM 对于一些近几年的知识以及非常识性的专业问题，回答的并不是很好。而加上我们的本地知识，就可以帮助 LLM 做出更好的回答。另外，也有助于缓解大模型的“幻觉”问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad20c510-f583-42b4-ac5c-b232388e9673",
   "metadata": {},
   "source": [
    "## 5. 添加历史对话的记忆功能"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "321e43e5-262d-4358-afb4-129dbf413287",
   "metadata": {},
   "source": [
    "现在我们已经实现了通过上传本地知识文档，然后将他们保存到向量知识库，通过将查询问题与向量知识库的召回结果进行结合输入到 LLM 中，我们就得到了一个相比于直接让 LLM 回答要好得多的结果。在与语言模型交互时，你可能已经注意到一个关键问题 - **它们并不记得你之前的交流内容**。这在我们构建一些应用程序（如聊天机器人）的时候，带来了很大的挑战，使得对话似乎缺乏真正的连续性。这个问题该如何解决呢？\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8cadc3df-4123-4e12-9516-8632b10dc41f",
   "metadata": {},
   "source": [
    "### 5.1 记忆（Memory）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e19f3fd2-14de-4177-893c-53ddaf6768e9",
   "metadata": {},
   "source": [
    "在本节中我们将介绍 LangChain 中的储存模块，即如何将先前的对话嵌入到语言模型中的，使其具有连续对话的能力。我们将使用 `ConversationBufferMemory` ，它保存聊天消息历史记录的列表，这些历史记录将在回答问题时与问题一起传递给聊天机器人，从而将它们添加到上下文中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "42210b88-3590-47dc-a087-ab9cef14f00c",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/4f/lx3tlt3d6fz2bys8ct46fzxh0000gn/T/ipykernel_90332/2228008247.py:3: LangChainDeprecationWarning: Please see the migration guide at: https://python.langchain.com/docs/versions/migrating_memory/\n",
      "  memory = ConversationBufferMemory(\n"
     ]
    }
   ],
   "source": [
    "from langchain.memory import ConversationBufferMemory\n",
    "\n",
    "memory = ConversationBufferMemory(\n",
    "    memory_key=\"chat_history\",  # 与 prompt 的输入变量保持一致。\n",
    "    return_messages=True  # 将以消息列表的形式返回聊天记录，而不是单个字符串\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48d580d9-b926-462f-9d6a-23135ecece37",
   "metadata": {},
   "source": [
    "关于更多的 Memory 的使用，包括保留指定对话轮数、保存指定 token 数量、保存历史对话的总结摘要等内容，请参考 langchain 的 Memory 部分的相关文档。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2ec5f5c-5ee8-4b89-add3-25f3c864200d",
   "metadata": {},
   "source": [
    "### 5.2 对话检索链（ConversationalRetrievalChain）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65f1afd4-a5e7-4eea-855b-d03055c93e2d",
   "metadata": {},
   "source": [
    "对话检索链（ConversationalRetrievalChain）在检索 QA 链的基础上，增加了处理对话历史的能力。\n",
    "\n",
    "它的工作流程是:\n",
    "1. 将之前的对话与新问题合并生成一个完整的查询语句。\n",
    "2. 在向量数据库中搜索该查询的相关文档。\n",
    "3. 获取结果后,存储所有答案到对话记忆区。\n",
    "4. 用户可在 UI 中查看完整的对话流程。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0305623-1a06-4bb8-b340-dde57202dd67",
   "metadata": {},
   "source": [
    "这种链式方式将新问题放在之前对话的语境中进行检索，可以处理依赖历史信息的查询。并保留所有信\n",
    "息在对话记忆中，方便追踪。\n",
    "\n",
    "接下来让我们可以测试这个对话检索链的效果："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05dd113e-9d21-4385-a426-7e7a8ebb887a",
   "metadata": {},
   "source": [
    "使用上一节中的向量数据库和 LLM ！首先提出一个无历史对话的问题“这门课会学习 Python 吗？”，并查看回答。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "2d5dcc5f-545e-463a-9f3f-9eca90457caa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "南瓜书是根据提供的上下文看起来是一本关于机器学习的书籍，名为《机器学习公式详解》。这本书由一个编委会负责，其中包括主编和编委，并且有一个封面设计团队。书中包含了机器学习的基本概念、模型评估与选择等内容，并且力图以本科数学基础的视角对公式进行解析和推导。此外，南瓜书还提供了配套的视频教程和在线阅读地址，以及通过GitHub平台收集读者反馈的方式。\n"
     ]
    }
   ],
   "source": [
    "from langchain.chains import ConversationalRetrievalChain\n",
    "\n",
    "retriever=vectordb.as_retriever()\n",
    "\n",
    "qa = ConversationalRetrievalChain.from_llm(\n",
    "    llm,\n",
    "    retriever=retriever,\n",
    "    memory=memory\n",
    ")\n",
    "question = \"什么是南瓜书？\"\n",
    "result = qa({\"question\": question})\n",
    "print(result['answer'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ad80344-7079-4fa2-a3f6-065f1a0a2d79",
   "metadata": {},
   "source": [
    "然后基于答案进行下一个问题“为什么这门课需要教这方面的知识？”："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "83ef314c-515d-499c-8358-f19f188895b4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "南瓜书主要包含以下内容和特点：\n",
      "\n",
      "1. 以本科数学基础的视角对机器学习中的公式进行解析和推导。\n",
      "2. 针对超纲的数学知识，通常以附录和参考文献的形式给出，方便感兴趣的同学深入学习。\n",
      "3. 提供了一个GitHub的Issues页面（地址：https://github.com/datawhalechina/pumpkin-book/issues），供读者反馈错误或提出希望补充的公式编号。\n",
      "4. 对于反馈，编委会承诺通常在24小时内回复。\n",
      "5. 提供了微信联系方式（微信号：at-Sm1les），以便在超过24小时未回复的情况下与编委会联系。\n",
      "6. 配套视频教程，可以在Bilibili上观看（地址：https://www.bilibili.com/video/BV1Mh411e7VU）。\n",
      "7. 提供在线阅读地址（https://datawhalechina.github.io/pumpkin-book），仅供第1版。\n",
      "8. 最新版PDF可以在GitHub的发布页面上获取（地址：https://github.com/datawhalechina/pumpkin-book/releases）。\n",
      "9. 包含了从绪论到模型评估与选择等章节，具体内容涵盖引言、基本术语、假设空间、归纳偏好、经验误差与过拟合、评估方法以及算法参数（超参数）与模型参数等。\n",
      "\n",
      "以上内容摘自提供的文本，具体书中内容可能更加丰富。\n"
     ]
    }
   ],
   "source": [
    "question = \"南瓜书包含哪些内容？\"\n",
    "result = qa({\"question\": question})\n",
    "print(result['answer'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10b44d8b-145d-42fc-b790-476798efdabe",
   "metadata": {},
   "source": [
    "可以看到，LLM 它准确地判断了这方面的知识，指代内容是强化学习的知识，也就\n",
    "是我们成功地传递给了它历史信息。这种持续学习和关联前后问题的能力，可大大增强问答系统的连续\n",
    "性和智能水平。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e49d86b8-7968-4a87-8bd5-9bceb5f65aa6",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "96d3f2be-19aa-428e-b991-3755e5628c76",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ec6cd22-6607-45da-802b-dd892e3c60ec",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain",
   "language": "python",
   "name": "langchain"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
